﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Data.Entity.Infrastructure;
using System.Web;
using com.paypal.sdk.util;

namespace ECSE6770
{
    public class Cart
    {
        const string REQUEST_KEY = "ECSE6770.Cart.ProductEntityItems";
        const string SESSION_KEY = "ECSE6770.Cart";
        static readonly object padlock = new object();

        private Cart()
        {
            _items = new Dictionary<int, CartItemWrapper>();
        }

        private Dictionary<int, CartItemWrapper> _items;

        /// <summary>
        /// Gets a list of the IDs, price, and teaser of the items that are in the cart.
        /// </summary>
        public ReadOnlyCollection<CartItemWrapper> Items
        {
            get { return _items.Values.ToList().AsReadOnly(); }
        }

        /// <summary>
        /// Returns the entity objects for the items that are in the cart.
        /// Calling this method multiple times per request probably won't kill performance,
        /// because Entity Framework maintains a cache of items (pretty sure about this one...).
        /// </summary>
        /// <returns></returns>
        public ReadOnlyCollection<Product> ProductEntityItems()
        {
            object cached = HttpContext.Current.Items[REQUEST_KEY];
            if (cached == null)
            {
                var ids = _items.Values.Select(i => i.Id).ToArray();
                var products = from p in ContextManager.CurrentDb.Products
                               where ids.Contains(p.Id)
                               select p;
                var list = products.ToList().AsReadOnly();
                HttpContext.Current.Items[REQUEST_KEY] = list;
                return list;
            }
            else
                return (ReadOnlyCollection<Product>)cached;
        }

        public int Count
        {
            get { return _items.Count; }
        }

        public decimal TotalCost
        {
            get { return _items.Values.Aggregate(0m, (total, buyable) => buyable.Price + total); }
        }

        public string TotalCostString
        {
            get { return String.Format("{0:c}", TotalCost); }
        }

        public string CartText()
        {
            return string.Format("My Cart ({0})", Count);
        }

        public bool InCart(Product p)
        {
            return InCart(p.Id);
        }

        public bool InCart(int productId)
        {
            return _items.ContainsKey(productId);
        }

        /// <summary>
        /// This cart assumes we can only have 1 quantity of any given item.
        /// This should hold true for this limited example.
        /// </summary>
        /// <param name="buyable"></param>
        public void AddItem(Product p)
        {
            if (!_items.ContainsKey(p.Id))
                _items.Add(p.Id, new CartItemWrapper(p)); //I need to avoid storing objects tracked by Entity Framework in session, cause it'll cause problems later.
        }

        public void RemoveItem(int id)
        {
            if (_items.ContainsKey(id))
                _items.Remove(id);
        }

        public void RemoveItem(Product p)
        {
            RemoveItem(p.Id);
        }

        public void Clear()
        {
            _items.Clear();
        }

        /// <summary>
        /// Writes all items in the cart to the Paypal encoder object.
        /// </summary>
        /// <param name="encoder"></param>
        public void WriteItems(NVPCodec encoder)
        {
            var products = this.ProductEntityItems().ToArray();
            for (int i = 0; i < products.Length; i++)
                products[i].AddToEncoder(encoder, i);
        }

        /// <summary>
        /// Calls the AddToUser method of each item in the cart, and saves changes to the data context.
        /// </summary>
        public void ApplyTransaction()
        {
            var user = ContextManager.CurrentUser.Interop;
            var products = this.ProductEntityItems();

            foreach (var p in products)
                user.PurchasedProducts.Add(p);

            ContextManager.CurrentDb.SaveChanges();
            
            Clear();
        }

        public static Cart Current
        {
            get
            {
                lock (padlock)
                {
                    Cart c = HttpContext.Current.Session[SESSION_KEY] as Cart;
                    if (c == null)
                    {
                        c = new Cart();
                        HttpContext.Current.Session[SESSION_KEY] = c;
                    }
                    return c;
                }
            }
        }
    }
}